//--------------------------------------------------------------------------------------
// File: ToonShading.cpp
//--------------------------------------------------------------------------------------
#include "Preheaders.h"
#include "SilhouetteEdges.h"

CFirstPersonCamera g_camera;
ID3DXMesh *g_teapot;
SilhouetteEdges	*g_teapotEdges;
D3DXMATRIX		g_world;
D3DXMATRIX		g_worldInvTran;
IDirect3DTexture9 *g_toonShadeTexture;

ID3DXEffect		*g_toonFX;
D3DXHANDLE		g_hToonTech;
D3DXHANDLE		g_hToonWorldInvTran;
D3DXHANDLE		g_hToonWVP;
D3DXHANDLE		g_hToonColor;
D3DXHANDLE		g_hToonLightDir;
D3DXHANDLE		g_hToonShade;

ID3DXEffect		*g_outlineFX;
D3DXHANDLE		g_hOutlineTech;
D3DXHANDLE		g_hOutlineWorld;
D3DXHANDLE		g_hOutlineWorldInvTran;
D3DXHANDLE		g_hOutlineEyePos;
D3DXHANDLE		g_hOutlineVP;
D3DXHANDLE		g_hOutlineColor;


void CreateToonEffect(IDirect3DDevice9 *pd3dDevice);
void CreateOutlineEffect(IDirect3DDevice9 *pd3dDevice);
void DrawWithToonEffect(IDirect3DDevice9 *pd3dDeivce);
void DrawWithOutlineEffect(IDirect3DDevice9 *pd3dDevice);

//--------------------------------------------------------------------------------------
// Rejects any D3D9 devices that aren't acceptable to the app by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D9DeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
	bool bWindowed, void* pUserContext )
{
	// Typically want to skip back buffer formats that don't support alpha blending
	IDirect3D9* pD3D = DXUTGetD3D9Object();
	if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
		AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
		D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
	return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that will live through a device reset (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	HRESULT hr;
	D3DXCreateTextureFromFile(pd3dDevice, TEXT("toonshade.bmp"), &g_toonShadeTexture);

	ID3DXBuffer *adjacencyBuffer;
	V(D3DXCreateTeapot(pd3dDevice, &g_teapot, &adjacencyBuffer));
	g_teapotEdges = new SilhouetteEdges(pd3dDevice, g_teapot, adjacencyBuffer);

	CreateOutlineEffect(pd3dDevice);
	CreateToonEffect(pd3dDevice);

	D3DXMatrixIdentity(&g_world);
	D3DXMatrixIdentity(&g_worldInvTran);

	g_camera.SetViewParams(&D3DXVECTOR3(0.0f, 0.0f, -8.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f));
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that won't live through a device reset (D3DPOOL_DEFAULT) 
// or that are tied to the back buffer size 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	g_camera.SetProjParams(D3DX_PI / 4, pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height,
		1.0f, 1000.0f);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
	g_camera.FrameMove(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D9 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	HRESULT hr;

	// Clear the render target and the zbuffer 
	V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 45, 50, 170 ), 1.0f, 0 ) );

	// Render the scene
	if( SUCCEEDED( pd3dDevice->BeginScene() ) )
	{
		DrawWithToonEffect(pd3dDevice);
		DrawWithOutlineEffect(pd3dDevice);
		V( pd3dDevice->EndScene() );
	}
}


//--------------------------------------------------------------------------------------
// Handle messages to the application 
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
	bool* pbNoFurtherProcessing, void* pUserContext )
{
	g_camera.HandleMessages(hWnd, uMsg, wParam, lParam);
	return 0;
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
	ReleaseCOM(g_toonFX);
	ReleaseCOM(g_outlineFX);
	ReleaseCOM(g_teapot);
	ReleaseCOM(g_toonShadeTexture);
	delete g_teapotEdges;
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int )
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

	// Set the callback functions
	DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
	DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
	DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
	DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
	DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
	DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );
	DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
	DXUTSetCallbackMsgProc( MsgProc );
	DXUTSetCallbackFrameMove( OnFrameMove );

	// TODO: Perform any application-level initialization here

	// Initialize DXUT and create the desired Win32 window and Direct3D device for the application
	DXUTInit( true, true ); // Parse the command line and show msgboxes
	DXUTSetHotkeyHandling( true, true, true );  // handle the default hotkeys
	DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
	DXUTCreateWindow( L"EmptyProject" );
	DXUTCreateDevice( true, 640, 480 );

	// Start the render loop
	DXUTMainLoop();

	// TODO: Perform any application-level cleanup here

	return DXUTGetExitCode();
}


void CreateToonEffect(IDirect3DDevice9 *pd3dDevice)
{
	ID3DXBuffer *compilationErrors = NULL;
	D3DXCreateEffectFromFile(pd3dDevice, L"toon.fx", NULL, NULL, D3DXSHADER_DEBUG, NULL, &g_toonFX, &compilationErrors);
	if(compilationErrors)
	{
		MessageBoxA(0, (char*)compilationErrors->GetBufferPointer(), 0, 0);
		return;
	}

	g_hToonTech			= g_toonFX->GetTechniqueByName("ToonTech");
	g_hToonWorldInvTran	= g_toonFX->GetParameterByName(0, "gWorldInvTran");
	g_hToonWVP			= g_toonFX->GetParameterByName(0, "gWVP");
	g_hToonColor		= g_toonFX->GetParameterByName(0, "gColor");
	g_hToonLightDir		= g_toonFX->GetParameterByName(0, "gLightDir");
	g_hToonShade	= g_toonFX->GetParameterByName(0, "gToonShade");

}

void CreateOutlineEffect(IDirect3DDevice9 *pd3dDevice)
{
	ID3DXBuffer *compilationErrors = NULL;
	D3DXCreateEffectFromFile(pd3dDevice, L"outline.fx", NULL, NULL, D3DXSHADER_DEBUG, NULL, &g_outlineFX, &compilationErrors);
	if(compilationErrors)
	{
		MessageBoxA(0, (char*)compilationErrors->GetBufferPointer(), 0, 0);
		return;
	}

	g_hOutlineTech			= g_outlineFX->GetTechniqueByName("ToonTech");
	g_hOutlineWorld			= g_outlineFX->GetParameterByName(0, "gWorld");
	g_hOutlineWorldInvTran	= g_outlineFX->GetParameterByName(0, "gWorldInvTran");
	g_hOutlineEyePos		= g_outlineFX->GetParameterByName(0, "gEyePos");
	g_hOutlineVP			= g_outlineFX->GetParameterByName(0, "gVP");
	g_hOutlineColor			= g_outlineFX->GetParameterByName(0, "gColor");

}

void DrawWithToonEffect(IDirect3DDevice9 *pd3dDeivce)
{
	D3DXMATRIX view = *g_camera.GetViewMatrix();
	D3DXMATRIX proj = *g_camera.GetProjMatrix();

	g_toonFX->SetTechnique(g_hToonTech);
	g_toonFX->SetMatrix(g_hToonWorldInvTran, &g_worldInvTran);
	g_toonFX->SetMatrix(g_hToonWVP, &(g_world * view * proj));
	g_toonFX->SetValue(g_hToonColor, &D3DXCOLOR(1.0f, 1.0f, 0.0f, 1.0f), sizeof(D3DXCOLOR));
	g_toonFX->SetValue(g_hToonLightDir, &D3DXVECTOR3(0.0f, 2.0f, -2.0f), sizeof(D3DXVECTOR3));
	g_toonFX->SetTexture(g_hToonShade, g_toonShadeTexture);

	g_toonFX->Begin(NULL, 0);
	g_toonFX->BeginPass(0);

	g_teapot->DrawSubset(0);

	g_toonFX->EndPass();
	g_toonFX->End();
}


void DrawWithOutlineEffect(IDirect3DDevice9 *pd3dDevice)
{
	D3DXMATRIX view = *g_camera.GetViewMatrix();
	D3DXMATRIX proj = *g_camera.GetProjMatrix();
	
	g_outlineFX->SetTechnique(g_hOutlineTech);
	g_outlineFX->SetMatrix(g_hOutlineWorld, &g_world);
	g_outlineFX->SetMatrix(g_hOutlineWorldInvTran, &g_worldInvTran);
	g_outlineFX->SetMatrix(g_hOutlineVP, &(view * proj));
	g_outlineFX->SetValue(g_hOutlineEyePos, g_camera.GetEyePt(), sizeof(D3DXVECTOR3));
	g_outlineFX->SetValue(g_hOutlineColor, &D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f), sizeof(D3DXCOLOR));

	g_outlineFX->Begin(NULL, 0);
	g_outlineFX->BeginPass(0);

	g_teapotEdges->render();

	g_outlineFX->EndPass();
	g_outlineFX->End();
}
